package com.xu.GOF23.single;

//懒汉式  需要时才去创建
public class LazyMan {
    //同样的构造器私有

    //双重检测锁模式的懒汉式单例 DCL懒汉
    //为什么不直接在方法上加锁的原因?
    /*
    只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 instance=new Singleton()处的代码，而只有此行代码需要同步，因此就无需对后续调用使用同步。
    所有其他调用用于决定 instance 是非 null 的，并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。
    尽管如此，由于该方法是synchronized 的，需要为该方法的每一次调用付出同步的代价，即使只有第一次调用需要同步。
    为使此方法更为有效，一个被称为双重检查锁定的习语就应运而生了。这个想法是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价
      Synchronized在方法上是一个重量级锁操作(建议看一下锁升级步骤),是需要切换上下文的,很耗时,如果已经创建了一个对象,
      但是每次调用都需要重新加锁解锁切换上下文才能拿到_instance不合理,但是双重加锁就不一样儿了,一旦创建对象以后就不会进行锁操作了,效率提升很多!
     */
    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "进入构造器");//这打印只是为了看是否有多条线程进入构造器创建
    }

    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance() {
        if (lazyMan == null) {//只有当他为空的时候才创建
            synchronized (LazyMan.class) {//锁class 就只有一个锁
                if (lazyMan == null) {
                    lazyMan = new LazyMan();//不是一个原子性操作
                }
            }
        }
        return lazyMan;
         /*lazyMan = new LazyMan();其实经过了三个步骤
                    1.分配内存空间
                    2.执行构造方法(初始化对象 )
                    3.把这个对象指向这个空间
                    这就有可能在底层造成指令重排 比如正确的顺序是 123
                    假设现在变成132 了A 线程没问题 ，假设现在来了条线程B 由于A 先执行了第三步 把这个对象指向了内存空间 ，
                    B就会在外层 if (lazyMan == null) 判断lazyMan != null 就直接 走下面的 return  lazyMan; 此时lazyMan并没有完成构造
                     */
    }
    //当多线程并发时会出问题
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                getInstance();
            }).start();
        }
    }


}
